﻿//这是一个用C#编写的打印服务程序
//目前在WIN10/11上经常出现各种原因导致的客户机无法连接共享打印机问题，从而影响维护人员解决问题的效率
//此程序的功能是用命令行方式指定本机的打印机和监听端口，把本地USB打印机变成一个标准的网络打印机使用RAW协议打印的功能
//程序可以独立运行，也可以安装成一个系统服务后台运行，建议使用服务方式运行，每一个打印机可以创建一个不同的端口服务
//客户机添加打印机，使用TCP网络打印机添加，然后使用对应的驱动程序即可连接打印
//对一些比如HP 1XXX系列的老打印机，此程序测试暂不支持，对于主机端是WIN7系统的，建议使用传统共享方式打印
//程序设计:草原企鹅   微信:15242609 邮箱:tyq@qq.com,欢迎联系讨论

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Collections.Generic;
using System.Management;
using System.Configuration.Install;
using System.Reflection;
using System.ServiceProcess;
using System.Diagnostics;
using System.Linq;
using System.Security.Principal;
using System.Text.RegularExpressions;

namespace NetPrintShare
{
    public class Program : ServiceBase
    {
        private static readonly string LogFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PrintServer.log");
        private static BlockingCollection<TcpClient> _requestQueue = new BlockingCollection<TcpClient>();

        private ManualResetEvent serviceStarted;
        private int port;
        private string printname;
        private static string appName = AppDomain.CurrentDomain.FriendlyName;//获取当前程序的文件名

        static async Task Main(string[] args)
        {
          

            if (args.Length == 0)
            {
                ShowUsage();
                //Console.WriteLine("请打开WINDOWS命令提示符，以管理员模式运行后使用！\n按任意键退出......");
                //Console.ReadKey();
                EnsureRunningAsAdministrator();
                return;
            }

            switch (args[0].ToLower())
            {
                case "install":
                    if (args.Length < 3)
                    {
                        Console.WriteLine("请提供打印机名称和端口号参数");
                        return;
                    }
                    HandleInstall(args);
                    break;

                case "remove":
                    if (args.Length < 2)
                    {
                        Console.WriteLine("请提供端口号参数");
                        return;
                    }
                    HandleRemove(args);
                    break;

                case "list":
                    HandleList();
                    break;

                case "status":
                    ShowStatus();
                    break;

                case "standalone":
                    if (args.Length < 3)
                    {
                        Console.WriteLine("请提供打印机名称和端口号参数");
                        return;
                    }
                    await HandleStandaloneAsync(args);
                    break;

                default:
                    // 作为服务运行
                    ServiceBase.Run(new Program());
                    break;
            }
        }

        private static void EnsureRunningAsAdministrator()
        {
            string targetDirectory = @"C:\PrintServer"; // 在这里指定你的目标目录
            string currentDirectory = Directory.GetCurrentDirectory();
            string executableName = AppDomain.CurrentDomain.FriendlyName;
            string targetPath = Path.Combine(targetDirectory, executableName);

            // 检查当前路径是否为指定路径
            if (currentDirectory != targetDirectory)
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine($"当前程序所在路径为:{currentDirectory}\n为了管理方便，程序会自动复制到指定目录 {targetDirectory}运行！");
                Console.ResetColor();
                try
                {
                    // 确保目标目录存在
                    if (!Directory.Exists(targetDirectory))
                    {
                        Directory.CreateDirectory(targetDirectory);
                    }

                    // 将可执行文件复制到目标目录
                    File.Copy(Path.Combine(currentDirectory, executableName), targetPath, true);

                    // 构建命令行参数以从目标目录运行程序
                    string arguments = $"/k \"cd /d {targetDirectory} && {targetPath} {string.Join(" ", Environment.GetCommandLineArgs().Skip(1))}\"";

                    // 在目标目录中启动新进程
                    Process.Start(new ProcessStartInfo
                    {
                        FileName = "cmd.exe",
                        Arguments = arguments,
                        Verb = "runas", // 以管理员权限运行
                        UseShellExecute = true
                    });

                    // 退出当前进程
                    Environment.Exit(0);
                }
                catch (Exception ex)
                {
                    LogMessage($"移动和重启应用程序失败: {ex.Message}", true);
                    Console.WriteLine($"Error: {ex.Message}");
                }
            }
            else
            {
                // 如果已经在目标目录中，只需继续运行

                // 检查是否有命令行参数，如果没有，说明是通过双击启动的
                if (Environment.GetCommandLineArgs().Length == 1)
                {
                    // 创建一个批处理文件来显示帮助信息并保持命令提示符打开
                    string batchFilePath = Path.Combine(targetDirectory, "show_help_and_prompt.bat");
                    string batchContent = @"
                            @echo off
                            chcp 65001 >nul
                            echo 这是一个绕过微软WINDOWS共享打印机的网络打印服务器工具
                            echo 程序设计:草原企企鹅   微信:15242609
                            echo.
                            echo ---------------------------------------------------------
                            echo 命令用法:
                            echo   " + appName + @" STANDALONE ""打印机名称"" 端口号
                            echo                 ---运行网络打印服务
                            echo   " + appName + @" INSTALL ""打印机名称"" 端口号
                            echo                 ---安装网络打印服务
                            echo   " + appName + @" REMOVE 端口号
                            echo                 ---删除指定端口的打印服务
                            echo   " + appName + @" LIST
                            echo                 ---列出当前可用的打印机名称
                            echo   " + appName + @" STATUS
                            echo                 ---列出当前已安装的打印机服务
                            echo.
                            echo ----------------------------------------------------------
                            cmd /k
                            ";
                    File.WriteAllText(batchFilePath, batchContent, new System.Text.UTF8Encoding(false));

                    // 启动批处理文件
                    Process.Start(new ProcessStartInfo
                    {
                        FileName = "cmd.exe",
                        Arguments = $"/k cd /d {targetDirectory} && \"{batchFilePath}\"",
                        Verb = "runas",
                        UseShellExecute = true
                    });

                    // 退出当前进程
                    Environment.Exit(0);
                }
            }
        }

        public Program()
        {
            this.ServiceName = "网络打印服务";
        }
        protected override async void OnStart(string[] args)
        {

            try
            {
                // 解析命令行参数
                string[] commandLineArgs = Environment.GetCommandLineArgs();
                if (commandLineArgs.Length > 1)
                {
                    printname = commandLineArgs[1];
                    if (printname.Contains("_"))
                    {
                        // 替换下划线为空格
                        printname = printname.Replace("_", " ");
                    }
                    port = int.Parse(commandLineArgs[2]);
                    // 打印所有参数
                    //LogMessage("传递的参数:");
                    //for (int i = 0; i < commandLineArgs.Length; i++)
                    //{
                    //    LogMessage($"参数 {i}: {commandLineArgs[i]}");
                    //}
                }


                LogMessage($"启动打印服务器，打印机: {printname}, 端口: {port}");
                _ = Task.Run(() => ProcessQueueAsync(printname));

                await StartPrintServerAsync(printname, port);
                //starthttp(port);
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("网络打印服务", $"服务启动失败: {ex.Message}", EventLogEntryType.Error);
            }
        }

        protected override void OnStop()
        {
            // 停止服务逻辑
        }
        static async Task HandleStandaloneAsync(string[] args)
        {
            if (args.Length < 3)
            {
                ShowUsage();
                return;
            }

            string printerName = args[1];
            if (!int.TryParse(args[2], out int port))
            {
                Console.WriteLine("无效的端口号");
                return;
            }

            LogMessage($"启动打印服务器，打印机: {printerName}, 端口: {port}");

            // 启动请求处理任务
            _ = Task.Run(() => ProcessQueueAsync(printerName));

            await StartPrintServerAsync(printerName, port);
        }

        static void HandleInstall(string[] args)
        {
            if (args.Length < 3)
            {
                ShowUsage();
                return;
            }
            // 安装服务
            string printerName = args[1];
            int port = int.Parse(args[2]);

            // 使用正则表达式检查打印机名称是否以 "HP" 开头并且中间数字部分以 "10" 开头
            //
            //使用 `Regex.IsMatch` 方法来检查打印机名称。
            //正则表达式 ^HP.*\b10\d* 的含义：
            //^HP：表示名称以 "HP" 开头。
            //.*：表示任意字符（包括无字符）直到下一个条件。
            //\b10\d *：表示一个单词边界后跟随 "10" 开头的数字部分。
            //如果匹配成功，输出提示信息 "不支持的打印机型号" 并返回，避免继续执行安装服务的操作
            //1\d{3}：表示以 "1" 开头的四位数字
            //.*$：表示任意字符直到字符串结束。
            //RegexOptions.IgnoreCase：设置正则表达式为不区分大小写
            if (Regex.IsMatch(printerName, @"^HP.*1\d{3}.*$", RegexOptions.IgnoreCase))
            {
                Console.WriteLine("部分惠普老机型，比如1018,1007等不支持本软件的打印方式请使用其他方式访问！");
                return;
            }
            else
            {
                // 检查打印机名称是否存在
                List<string> installedPrinters = GetInstalledPrinters();
                if (!installedPrinters.Contains(printerName))
                {
                    Console.WriteLine($"你输入的打印机 '{printerName}' 本机并不存在，请检查输入是否正确(注意空格大小写)。\n");
                    Console.WriteLine($"可以使用{appName} list命令查看当前本机安装的所有打印机名称");
                    return;
                }
            }

            // 检查防火墙状态并添加防火墙规则
            if (IsFirewallEnabled())
            {
                AddFirewallRule(port);
            }
            ServiceManager.InstallService(printerName, port);
        }

        // 检查防火墙是否启用
        static bool IsFirewallEnabled()
        {
            Process process = new Process();
            process.StartInfo.FileName = "netsh";
            process.StartInfo.Arguments = "advfirewall show allprofiles state";
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.CreateNoWindow = true;
            process.Start();

            string output = process.StandardOutput.ReadToEnd();
            process.WaitForExit();

            return output.Contains("启用");
        }

        // 添加防火墙规则
        static void AddFirewallRule(int port)
        {
            Process process = new Process();
            process.StartInfo.FileName = "netsh";
            process.StartInfo.Arguments = $"advfirewall firewall add rule name=\"PrintServerPort{port}\" dir=in action=allow protocol=TCP localport={port}";
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.CreateNoWindow = true;
            process.Start();
            process.WaitForExit();

            Console.WriteLine($"已添加防火墙规则以允许端口 {port} 的流量。");
        }

        static void HandleRemove(string[] args)
        {
            if (args.Length < 1)
            {
                ShowUsage();
                return;
            }
            //string printerName = args[1];
            int port = int.Parse(args[1]);
            //卸载服务
            ServiceManager.UninstallService(port);
        }
        static void ShowUsage()
        {
            string title = "这是一个绕过微软WINDOWS共享打印机的网络打印服务器工具\n";
            string subtitle = "           程序设计:草原企鹅   微信:15242609";
            string separator = new string('-', Console.WindowWidth);

            Console.WriteLine(separator);
            Console.WriteLine(title.PadLeft(Console.WindowWidth/2));
            Console.WriteLine(subtitle.PadLeft(Console.WindowWidth/2 + 10));
            Console.WriteLine(separator);
            Console.WriteLine("命令用法:\n".PadRight(58));
            Console.WriteLine($"   {appName} STANDALONE \"打印机名称\" 端口号".PadRight(5));
            Console.WriteLine("                ---运行网络打印服务\n".PadRight(58));
            Console.WriteLine($"   {appName} INSTALL \"打印机名称\" 端口号".PadRight(58));
            Console.WriteLine("                ---安装网络打印服务\n".PadRight(58));
            Console.WriteLine($"   {appName} REMOVE 端口号".PadRight(58));
            Console.WriteLine("                ---删除指定端口的打印服务\n".PadRight(58));
            Console.WriteLine($"   {appName} LIST".PadRight(58));
            Console.WriteLine("                ---列出当前可用的打印机名称\n".PadRight(58));
            Console.WriteLine($"   {appName} STATUS".PadRight(58));
            Console.WriteLine("                ---列出当前已安装的打印机服务".PadRight(58));
            Console.WriteLine(separator);
        }

        static List<string> GetInstalledPrinters()
        {
            List<string> printers = new List<string>();
            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Printer"))
            {
                foreach (ManagementObject printer in searcher.Get())
                {
                    // 检查打印机的端口名称是否包含 "USB" 并且是本地打印机
                    string portName = printer["PortName"]?.ToString();
                    bool isLocal = (bool)printer["Local"];
                    
                    if (portName != null && portName.IndexOf("USB", StringComparison.OrdinalIgnoreCase) >= 0 && isLocal)
                    {
                        printers.Add(printer["Name"].ToString());
                    }
                }
            }
            return printers;
        }
        public static void ShowStatus()
            {
            ServiceManager.GetNetworkPrinterServices();
            }
        static void HandleList()
        {
            List<string> printers = GetInstalledPrinters();
            if (printers.Count > 1)
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("\n本机已安装的打印机列表(USB接口):\n");
                Console.ResetColor();
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("\n本机未安装任何本地打印机(USB接口):\n");
                Console.ResetColor();
            }

            Console.ForegroundColor = ConsoleColor.Green;
            foreach (string printer in printers)
            {
                Console.WriteLine(printer + "\n");
            }
            Console.ResetColor();
        }

        public static void LogMessage(string message, bool isError = false)
        {
            string logMessage = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}";
            Console.WriteLine(logMessage);
            File.AppendAllText(LogFilePath, logMessage + Environment.NewLine);
            if (isError)
            {
                // 可以添加额外的错误处理逻辑，如发送邮件通知等
            }
        }

        public static async Task StartPrintServerAsync(string printerName, int port)
        {
            TcpListener listener = null;
            try
            {
                listener = new TcpListener(IPAddress.Any, port);
                listener.Start();
                EventLog.WriteEntry("网络打印机服务", $"服务已启动，监听端口为：{port}", EventLogEntryType.Information);
                LogMessage($"打印机 {printerName} 正在监听端口 {port}...");

                while (true)
                {
                    TcpClient client = await listener.AcceptTcpClientAsync();
                    LogMessage("接受到新的打印请求");
                    _requestQueue.Add(client);
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("网络打印服务", $"处理请求时出错: {ex.Message}", EventLogEntryType.Error);
                LogMessage($"启动服务器时出错: {ex.Message}", true);
            }
            finally
            {
                listener?.Stop();
            }
        }

        static async Task ProcessQueueAsync(string printerName)
        {
            foreach (var client in _requestQueue.GetConsumingEnumerable())
            {
                try
                {
                    await HandleClientAsync(client, printerName);
                }
                catch (Exception ex)
                {
                    LogMessage($"处理打印请求时出错: {ex.Message}", true);
                }
            }
        }

        static async Task HandleClientAsync(TcpClient client, string printerName)
        {
            client.ReceiveTimeout = 5000;
            client.SendTimeout = 5000;

            try
            {
                using (NetworkStream stream = client.GetStream())
                using (MemoryStream ms = new MemoryStream())
                {
                    byte[] buffer = new byte[8192];
                    int bytesRead;
                    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                    {
                        ms.Write(buffer, 0, bytesRead);
                    }

                    byte[] printData = ms.ToArray();
                    LogMessage($"接收到 {printData.Length} 字节的打印数据");

                    if (RawPrintHelper.SendRawDataToPrinter(printerName, printData))
                    {
                        LogMessage("打印任务已成功发送到打印机");
                    }
                    else
                    {
                        LogMessage("发送打印任务时出错");
                    }
                }
            }
            catch (IOException ioEx)
            {
                LogMessage($"客户端连接超时或数据读取错误: {ioEx.Message}", true);
            }
            catch (SocketException sockEx)
            {
                LogMessage($"Socket异常: {sockEx.Message}", true);
            }
            catch (Exception ex)
            {
                LogMessage($"处理客户端请求时发生未预期的错误: {ex.Message}", true);
            }
            finally
            {
                client.Close(); // 确保客户端连接关闭
            }
        }
    }
}
